/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
    This file was generated by VMB using these parameters:

        Benchmarks Class:     $state_name
        Bench Setup:          $state_setup
        Bench Method:         $method_name -> $method_rettype
        Parameter Set:        #$fix_id $fixture
        Bench Full Name:      $bench_name
        WarmUp Iterations:    $wi
        Measure Iterations:   $mi
        Iteration Time (sec): $it
        Warmup Time (sec):    $wt
        Fast Iterations:      $fi
*/

$imports

// params
let WI: Int = $wi;
let MI: Int = $mi;
let IT: Int = $it;
let WT: Int = $wt;
let FI: Int = $fi;

let MAX_LOOP_COUNT = 10_000_000_000
var totalOps: Int = 0
var totalSec: Double = 0.0
var iter: Int = 1
var loopCount1: Int = 1

enum ConsumerError: Error {
    case unexpectedResult
}

class Consumer {
    static private let x1: Int64 = 0x41c64e6d
    static private let x2: Int64 = 0xd431
    static private var x3: Int64 = 1
    static var boola: Bool = false
    static var boolb: Bool = true
    static var chara: Character = "X"  // 16-bit
    static var charb: Character = "Y"
    static var bytea: Int8 = 24
    static var byteb: Int8 = 53
    static var shorta: Int16 = 24
    static var shortb: Int16 = 53
    static var inta: Int = 24
    static var intb: Int = 53
    static var longa: Int64 = 24
    static var longb: Int64 = 53
    static var floata: Float = 24.0
    static var floatb: Float = 53.0
    static var doublea: Double = 24.0
    static var doubleb: Double = 53.0
    static var pseudorand: Int64 = Int64.random(in: 1..<1_000_000)
    static var localObj: AnyObject? = nil
    static var localObjs: [AnyObject] = []

    class func consumeBool(_ v: Bool) throws {
        if (v == boola && v == boolb) {
            throw ConsumerError.unexpectedResult
        }
    }

    class func consumeChar(_ v: Character) throws {
        if (v == chara && v == charb) {
            throw ConsumerError.unexpectedResult
        }
    }

    class func consumeShort(_ v: Int16) throws {
        if (v == shorta && v == shortb) {
            throw ConsumerError.unexpectedResult
        }
    }

    class func consumeInt(_ v: Int) throws {
        if (v == inta && v == intb) {
            throw ConsumerError.unexpectedResult
        }
    }

    class func consumeLong(_ v: Int64) throws {
        if (v == longa && v == longb) {
            throw ConsumerError.unexpectedResult
        }
    }

    class func consumeFloat(_ v: Float) throws {
        if (v == floata && v == floatb) {
            throw ConsumerError.unexpectedResult
        }
    }

    class func consumeDouble(_ v: Double) throws {
        if (v == doublea && v == doubleb) {
            throw ConsumerError.unexpectedResult
        }
    }

    class func consumeObj(_ v: AnyObject) {
        // NOTE: these lines ported from other langs
        // cause compiled binary to crash
        // pseudorand = (pseudorand * x1 + x2)
        // if ((pseudorand & x3) == 0) {
        //     x3 = (x3 << 1) + 0xad
        //     localObj = v
        // }
        if (pseudorand > x3) {
            localObj = v
        }
    }

    class func consumeObjs(_ v: [AnyObject]) {
        if (pseudorand > x3) {
            localObjs = v
        }
    }

    class func consume(_ v: Bool) throws {
        try consumeBool(v)
    }

    class func consume(_ v: Character) throws {
        try consumeChar(v)
    }

    class func consume(_ v: Int16) throws {
        try consumeShort(v)
    }

    class func consume(_ v: Int) throws {
        try consumeInt(v)
    }

    class func consume(_ v: Int64) throws {
        try consumeLong(v)
    }

    class func consume(_ v: Float) throws {
        try consumeFloat(v)
    }

    class func consume(_ v: Double) throws {
        try consumeDouble(v)
    }

}

extension Duration {
    var toDouble: Double {
        return Double(components.seconds) + Double(components.attoseconds) * 1e-18
    }
}

$common

$src

// no Foundation, so no Date()
print("Startup execution started: 0")

let clock = ContinuousClock()
let bench = $state_name()
$state_params
$state_setup

func tune() throws {
    var secPerLoop: Double = 0.0
    var loopCount: Int = 1
    while (secPerLoop < 1.0 && loopCount < MAX_LOOP_COUNT) {
        loopCount = loopCount * 2
        secPerLoop = try clock.measure {
            for _ in 0..<loopCount {
                $method_call
            }
        }.toDouble
    }
    if (secPerLoop < 1.0) {
        print("ERROR: Tuning Failed.")
        loopCount1 = MAX_LOOP_COUNT
    } else {
        loopCount1 = Int(Double(loopCount)/secPerLoop)
        if (loopCount1 == 0) {
            loopCount1 = 1
        }
    }
    print("Tuning: \(loopCount) ops, \(secPerLoop*1e9/Double(loopCount)) ns/op => \(loopCount1) reps")
}

func runIters(phase: String, count: Int, time: Int) throws {
    totalOps = 0
    totalSec = 0.0
    for _ in 1...count {
        var ops: Int = 0
        var elapsed: Double = 0.0
        while (elapsed < Double(time)) {
            elapsed += try clock.measure {
                for _ in 0..<loopCount1 {
                    $method_call
                }
            }.toDouble
            ops += loopCount1
        }
        totalOps += ops
        totalSec += elapsed
        let nsOps = elapsed*1e9/Double(ops)
        print("\(phase) \(iter):\(ops) ops, \(nsOps) ns/op")
        iter += 1
    }
}


if (FI > 0) {
    print("Fast starting $bench_name @ ${fixture}...");
    var elapsed: Double = try clock.measure {
        for _ in 0...FI {
            $method_call
        }
    }.toDouble
    if (elapsed <= 0) {
        elapsed = 1  // 0 is invalid result
    }
    print("Benchmark result: $bench_name \(1.0*elapsed/Double(FI))")
} else {
    try tune()
    if (WI > 0) {
        iter = 1
        for _ in 0..<WI {
            try runIters(phase: "Warmup", count: 1, time: WT)
        }
    }
    iter = 1
    try runIters(phase: "Iter", count: MI, time: IT)
    print("Benchmark result: $bench_name \(totalSec*1e9/Double(totalOps))")
}
// Teardown
Consumer.consumeObj(bench)
